#ifndef _NE_COLOR_H_
#define _NE_COLOR_H_

#include <mathematics/Math.h>

namespace ne
{
    class NEAPI Color
    {
        public:
            inline Color(const real red = 0.0f,
                         const real green = 0.0f,
                         const real blue = 0.0f,
                         const real alpha = 1.0f)
                : r(red), g(green), b(blue), a(alpha)
            {
            }

            inline Color(const int red,
                         const int green = 0,
                         const int blue = 0,
                         const int alpha = 0xFF)
                : r(real(red) / 255.0f),
                  g(real(green) / 255.0f),
                  b(real(blue) / 255.0f),
                  a(real(alpha) / 255.0f)
            {
            }

            inline explicit Color(const real* const v)
                : r(v[0]), g(v[1]), b(v[2]), a(v[3])
            {
            }

            inline explicit Color(const int* const v)
                : r(real(v[0]) / 255.0f),
                  g(real(v[1]) / 255.0f),
                  b(real(v[2]) / 255.0f),
                  a(real(v[3]) / 255.0f)
            {
            }

            inline Color(const Color &v)
                : r(v.r), g(v.g), b(v.b), a(v.a)
            {
            }

            inline real operator [] (const size_t v) const
            {
                assert(v < 4);
                return *(&r + v);
            }

            inline real& operator [] (const size_t v)
            {
                assert(v < 4);
                return *(&r + v);
            }

            inline real* ptr()
            {
                return &r;
            }

            inline const real* ptr() const
            {
                return &r;
            }

            // Evaluate
            inline Color& operator = (const Color &v)
            {
                r = v.r;
                g = v.g;
                b = v.b;
                a = v.a;
                return *this;
            }

            // Equal
            inline bool operator == (const Color &v) const
            {
                return (r == v.r) && (g == v.g) && (b == v.b) && (a == v.a);
            }

            // Inequivalence
            inline bool operator != (const Color &v) const
            {
                return (r != v.r) || (g != v.g) || (b != v.b) || (a != v.a);
            }

            inline Color operator + (const real v) const
            {
                return Color(r + v, g + v, b + v, a + v);
            }

            inline Color operator + (const Color &v) const
            {
                return Color(r + v.r, g + v.g, b + v.b, a + v.a);
            }

            NEAPI friend Color operator + (const real vl, const Color &vr);

            inline Color operator - (const real v) const
            {
                return Color(r - v, g - v, b - v, a - v);
            }

            inline Color operator - (const Color &v) const
            {
                return Color(r - v.r, g - v.g, b - v.b, a - v.a);
            }

            NEAPI friend Color operator - (const real vl, const Color &vr);

            inline Color operator * (const real v) const
            {
                return Color(r * v, g * v, b * v, a * v);
            }

            inline Color operator * (const Color &v) const
            {
                return Color(r * v.r, g * v.g, b * v.b, a * v.a);
            }

            NEAPI friend Color operator * (const real vl, const Color &vr);

            inline Color operator / (const real v) const
            {
                assert(v != 0.0f);
                return Color(r / v, g / v, b / v, a / v);
            }

            inline Color operator / (const Color &v) const
            {
                assert((v.r != 0.0f) && (v.g != 0.0f) && (v.b != 0.0f) && (v.a != 0.0f));
                return Color(r / v.r, g / v.g, b / v.b, a / v.a);
            }

            NEAPI friend Color operator / (const real vl, const Color &vr);

            inline Color& operator += (const real v)
            {
                r += v;
                g += v;
                b += v;
                a += v;
                return *this;
            }

            inline Color& operator += (const Color &v)
            {
                r += v.r;
                g += v.g;
                b += v.b;
                a += v.a;
                return *this;
            }

            inline Color& operator -= (const real v)
            {
                r -= v;
                g -= v;
                b -= v;
                a -= v;
                return *this;
            }

            inline Color& operator -= (const Color &v)
            {
                r -= v.r;
                g -= v.g;
                b -= v.b;
                a -= v.a;
                return *this;
            }

            inline Color& operator *= (const real v)
            {
                r *= v;
                g *= v;
                b *= v;
                a *= v;
                return *this;
            }

            inline Color& operator *= (const Color &v)
            {
                r *= v.r;
                g *= v.g;
                b *= v.b;
                a *= v.a;
                return *this;
            }

            inline Color& operator /= (const real v)
            {
                assert(v);
                r /= v;
                g /= v;
                b /= v;
                a /= v;
                return *this;
            }

            inline Color& operator /= (const Color &v)
            {
                assert((v.r != 0.0f) && (v.g != 0.0f) && (v.b != 0.0f) && (v.a != 0.0f));
                r /= v.r;
                g /= v.g;
                b /= v.b;
                a /= v.a;
                return *this;
            }

            inline void clamp()
            {
                Math::Clamp(r);
                Math::Clamp(g);
                Math::Clamp(b);
                Math::Clamp(a);
            }

#if NE_ENDIAN == NE_ENDIAN_LITTLE
            inline uint32_t getRGBA() const
#else
            inline uint32_t getABGR() const
#endif
            {
                uint32_t v = static_cast<byte>(a * 255.0f) << 24;
                v += static_cast<byte>(b * 255.0f) << 16;
                v += static_cast<byte>(g * 255.0f) << 8;
                v += static_cast<byte>(r * 255.0f);
                return v;
            }

#if NE_ENDIAN == NE_ENDIAN_LITTLE
            inline uint32_t getABGR() const
#else
            inline uint32_t getRGBA() const
#endif
            {
                uint32_t v = static_cast<byte>(r * 255.0f) << 24;
                v += static_cast<byte>(g * 255.0f) << 16;
                v += static_cast<byte>(b * 255.0f) << 8;
                v += static_cast<byte>(a * 255.0f);
                return v;
            }

            /** Set a colour value from Hue, Saturation and Brightness.
    		@param hue Hue value, scaled to the [0,1] range as opposed to the 0-360
    		@param saturation Saturation level, [0,1]
    		@param brightness Brightness level, [0,1]
    		*/
    		void setHSB(const real hue, const real saturation, const real brightness);
    
    		/** Convert the current colour to Hue, Saturation and Brightness values. 
    		@param hue Output hue value, scaled to the [0,1] range as opposed to the 0-360
    		@param saturation Output saturation level, [0,1]
    		@param brightness Output brightness level, [0,1]
    		*/
    		void getHSB(real &hue, real &saturation, real &brightness) const;
 
            // Function for writing to a stream.
            NEAPI friend std::ostream& operator << (std::ostream &o, const Color &c);

            inline ~Color() {}

        public:
            static bool Equal(const Color &vS, const Color &vD, const real tolerance=Math::EPSILON);

            static Color Mid(const Color &vS, const Color &vD);

            static Color Lerp(const Color &vS, const Color &vD, const real u);

            static Color Bezier(const Color &vS, const Color &vA, const Color &vB, const Color &vD, const real u);

        public:
            static const Color ZERO;
            static const Color BLACK;
            static const Color DARK;
            static const Color LIGHT;
            static const Color GRAY;
            static const Color WHITE;
            static const Color RED;
            static const Color GREEN;
            static const Color BLUE;
            static const Color YELLOW;
            static const Color PURPLE;
            static const Color CYAN;
            static const Color ORANGE;
            static const Color SKY;

        public:
            real r, g, b, a;
    };
}

#endif
